/*
 *  Copyright 2006 The Apache Software Foundation
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.apache.ibatis.ibator.internal.java.model;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import org.apache.ibatis.ibator.api.CommentGenerator;
import org.apache.ibatis.ibator.api.FullyQualifiedTable;
import org.apache.ibatis.ibator.api.GeneratedJavaFile;
import org.apache.ibatis.ibator.api.IbatorPlugin;
import org.apache.ibatis.ibator.api.IntrospectedTable;
import org.apache.ibatis.ibator.api.JavaModelGenerator;
import org.apache.ibatis.ibator.api.ProgressCallback;
import org.apache.ibatis.ibator.api.dom.OutputUtilities;
import org.apache.ibatis.ibator.api.dom.java.Field;
import org.apache.ibatis.ibator.api.dom.java.FullyQualifiedJavaType;
import org.apache.ibatis.ibator.api.dom.java.InnerClass;
import org.apache.ibatis.ibator.api.dom.java.JavaVisibility;
import org.apache.ibatis.ibator.api.dom.java.Method;
import org.apache.ibatis.ibator.api.dom.java.Parameter;
import org.apache.ibatis.ibator.api.dom.java.TopLevelClass;
import org.apache.ibatis.ibator.config.IbatorContext;
import org.apache.ibatis.ibator.config.PropertyRegistry;
import org.apache.ibatis.ibator.internal.IbatorObjectFactory;
import org.apache.ibatis.ibator.internal.db.ColumnDefinition;
import org.apache.ibatis.ibator.internal.rules.IbatorRules;
import org.apache.ibatis.ibator.internal.util.JavaBeansUtil;
import org.apache.ibatis.ibator.internal.util.StringUtility;
import org.apache.ibatis.ibator.internal.util.messages.Messages;

/**
 * This class supports the following properties:
 * 
 * <dl>
 * <dt>trimStrings
 * <dt>
 * <dd>If true, the setters will trim all Strings. Default is false.</dd>
 * 
 * <dt>enableSubPackages
 * <dt>
 * <dd>If true, the classes will be generated in sub-packaged based on the
 * database catalg and schema - else the will be generated in the specified
 * package (the targetPackage attribute). Default is false.</dd>
 * 
 * <dt>rootClass
 * <dt>
 * <dd>If specified, then the root class of all objects generated by the
 * generator will be used as specified. No checking is done to see if the
 * specified class exists, or if the generated classes hide any attributes or
 * methods in the specified class. Note that the root class is not the base
 * class of all objects - just the root class. For example, if there is a
 * primary key then the primary key will extend the root class and the record
 * class will still extend the primary key.</dd>
 * </dl>
 * 
 * @author Jeff Butler
 */
public class JavaModelGeneratorJava2Impl implements JavaModelGenerator {

    protected IbatorContext ibatorContext;
    protected List<String> warnings;

    /**
     * The properties from the JavaModelGenerator congiguration element
     */
    protected Properties properties;

    private Map<String, PropertyDescriptor[]> propertyDescriptorMap;
    
    public JavaModelGeneratorJava2Impl() {
        super();
        properties = new Properties();
        propertyDescriptorMap = new HashMap<String, PropertyDescriptor[]>();
    }

    public void addConfigurationProperties(Properties properties) {
        this.properties.putAll(properties);
    }

    /**
     * Adds fields and getter/setter methods for each ColumnDefinition passed
     * into the method.
     * 
     * @param table
     *            the table from which the ColumnDefinitions are derived. This
     *            is used to generate appropriate JavaDoc comments for the
     *            generated fields and methods.
     * @param columnDefinitions
     *            the collection of ColumnDefinitions used to generate fields
     *            and getter/setter methods.
     * @param topLevelClass
     *            the generated fields and methods will be added to this object
     * @param introspectedTable the current table
     */
    protected void generateClassParts(FullyQualifiedTable table,
            List<ColumnDefinition> columnDefinitions, TopLevelClass topLevelClass,
            IntrospectedTable introspectedTable) {

        IbatorPlugin plugins = ibatorContext.getPlugins();
        String rootClass = getRootClass(introspectedTable);
        
        boolean trimStrings = StringUtility.isTrue(properties
                .getProperty(PropertyRegistry.MODEL_GENERATOR_TRIM_STRINGS));

        StringBuffer sb = new StringBuffer();
        Field field;
        Method method;

        CommentGenerator commentGenerator = ibatorContext.getCommentGenerator();

        for (ColumnDefinition columnDefinition : columnDefinitions) {
            if (propertyExistsInRootClass(columnDefinition, rootClass)) {
                continue;
            }
            
            FullyQualifiedJavaType fqjt = columnDefinition.getResolvedJavaType()
                    .getFullyQualifiedJavaType();

            topLevelClass.addImportedType(fqjt);

            String property = columnDefinition.getJavaProperty();

            field = new Field();
            field.setVisibility(JavaVisibility.PRIVATE);
            field.setType(fqjt);
            field.setName(property);
            commentGenerator.addFieldComment(field, table, columnDefinition.getActualColumnName());
        
            if (plugins.modelFieldGenerated(field, topLevelClass, columnDefinition, introspectedTable)) {
                topLevelClass.addField(field);
            }

            method = new Method();
            method.setVisibility(JavaVisibility.PUBLIC);
            method.setReturnType(fqjt);
            method.setName(JavaBeansUtil.getGetterMethodName(field.getName(), field.getType()));
            commentGenerator.addGetterComment(method, table, columnDefinition.getActualColumnName());
            sb.setLength(0);
            sb.append("return "); //$NON-NLS-1$
            sb.append(property);
            sb.append(';');
            method.addBodyLine(sb.toString());

            if (plugins.modelGetterMethodGenerated(method, topLevelClass, columnDefinition, introspectedTable)) {
                topLevelClass.addMethod(method);
            }

            method = new Method();
            method.setVisibility(JavaVisibility.PUBLIC);
            method.setName(JavaBeansUtil.getSetterMethodName(property));
            method.addParameter(new Parameter(fqjt, property));
            commentGenerator.addSetterComment(method, table, columnDefinition.getActualColumnName());

            if (trimStrings && columnDefinition.isStringColumn()) {
                sb.setLength(0);
                sb.append("this."); //$NON-NLS-1$
                sb.append(property);
                sb.append(" = "); //$NON-NLS-1$
                sb.append(property);
                sb.append(" == null ? null : "); //$NON-NLS-1$
                sb.append(property);
                sb.append(".trim();"); //$NON-NLS-1$
                method.addBodyLine(sb.toString());
            } else {
                sb.setLength(0);
                sb.append("this."); //$NON-NLS-1$
                sb.append(property);
                sb.append(" = "); //$NON-NLS-1$
                sb.append(property);
                sb.append(';');
                method.addBodyLine(sb.toString());
            }
            
            if (plugins.modelSetterMethodGenerated(method, topLevelClass, columnDefinition, introspectedTable)) {
                topLevelClass.addMethod(method);
            }
        }
    }

    protected TopLevelClass getPrimaryKey(IntrospectedTable introspectedTable) {

        if (!introspectedTable.getRules().generatePrimaryKeyClass()) {
            return null;
        }

        FullyQualifiedTable table = introspectedTable.getFullyQualifiedTable();
        FullyQualifiedJavaType type = introspectedTable.getPrimaryKeyType();
        TopLevelClass answer = new TopLevelClass(type);
        answer.setVisibility(JavaVisibility.PUBLIC);
        ibatorContext.getCommentGenerator().addJavaFileComment(answer);

        String rootClass = getRootClass(introspectedTable);
        if (rootClass != null) {
            answer.setSuperClass(new FullyQualifiedJavaType(rootClass));
            answer.addImportedType(answer.getSuperClass());
        }
        
        generateClassParts(table, introspectedTable.getPrimaryKeyColumns(), answer,
                introspectedTable);

        return answer;
    }

    protected TopLevelClass getBaseRecord(IntrospectedTable introspectedTable) {

        if (!introspectedTable.getRules().generateBaseRecordClass()) {
            return null;
        }

        FullyQualifiedTable table = introspectedTable.getFullyQualifiedTable();
        FullyQualifiedJavaType type = introspectedTable.getBaseRecordType();
        TopLevelClass answer = new TopLevelClass(type);
        answer.setVisibility(JavaVisibility.PUBLIC);
        ibatorContext.getCommentGenerator().addJavaFileComment(answer);
        
        if (introspectedTable.getRules().generatePrimaryKeyClass()) {
            answer.setSuperClass(introspectedTable.getPrimaryKeyType());
        } else {
            String rootClass = getRootClass(introspectedTable);
            if (rootClass != null) {
                answer.setSuperClass(new FullyQualifiedJavaType(rootClass));
                answer.addImportedType(answer.getSuperClass());
            }
        }
        
        if (!introspectedTable.getRules().generatePrimaryKeyClass()
                && introspectedTable.hasPrimaryKeyColumns()) {
            generateClassParts(table, introspectedTable.getPrimaryKeyColumns(), answer,
                    introspectedTable);
        }

        generateClassParts(table, introspectedTable.getBaseColumns(), answer,
                introspectedTable);

        if (!introspectedTable.getRules().generateRecordWithBLOBsClass()
                && introspectedTable.hasBLOBColumns()) {
            generateClassParts(table, introspectedTable.getBLOBColumns(), answer,
                    introspectedTable);
        }
        
        return answer;
    }

    protected TopLevelClass getRecordWithBLOBs(IntrospectedTable introspectedTable) {

        if (!introspectedTable.getRules().generateRecordWithBLOBsClass()) {
            return null;
        }

        FullyQualifiedTable table = introspectedTable.getFullyQualifiedTable();
        FullyQualifiedJavaType type = introspectedTable.getRecordWithBLOBsType();
        TopLevelClass answer = new TopLevelClass(type);
        answer.setVisibility(JavaVisibility.PUBLIC);
        ibatorContext.getCommentGenerator().addJavaFileComment(answer);
        
        if (introspectedTable.getRules().generateBaseRecordClass()) {
            answer.setSuperClass(introspectedTable.getBaseRecordType());
        } else {
            answer.setSuperClass(introspectedTable.getPrimaryKeyType());
        }
        
        generateClassParts(table, introspectedTable.getBLOBColumns(), answer,
                introspectedTable);

        return answer;
    }

    /*
     *  (non-Javadoc)
     * @see org.apache.ibatis.ibator.api.JavaModelGenerator#getGeneratedJavaFiles(org.apache.ibatis.ibator.api.IntrospectedTable, org.apache.ibatis.ibator.api.ProgressCallback)
     */
    public List<GeneratedJavaFile> getGeneratedJavaFiles(IntrospectedTable introspectedTable, ProgressCallback callback) {
        List<GeneratedJavaFile> list = new ArrayList<GeneratedJavaFile>();
        IbatorPlugin plugins = ibatorContext.getPlugins();
        String targetProject = ibatorContext.getJavaModelGeneratorConfiguration().getTargetProject();

        callback.startSubTask(Messages.getString(
                "Progress.6", //$NON-NLS-1$
                introspectedTable.getFullyQualifiedTable().toString()));
        TopLevelClass tlc = getExample(introspectedTable);
        if (tlc != null) {
            if (plugins.modelExampleClassGenerated(tlc, introspectedTable)) {
                GeneratedJavaFile gjf = new GeneratedJavaFile(tlc, targetProject);
                list.add(gjf);
            }
        }

        callback.startSubTask(Messages.getString(
                "Progress.7", //$NON-NLS-1$
                introspectedTable.getFullyQualifiedTable().toString()));
        tlc = getPrimaryKey(introspectedTable);
        if (tlc != null) {
            if (plugins.modelPrimaryKeyClassGenerated(tlc, introspectedTable)) {
                GeneratedJavaFile gjf = new GeneratedJavaFile(tlc, targetProject);
                list.add(gjf);
            }
        }

        callback.startSubTask(Messages.getString(
                "Progress.8", //$NON-NLS-1$
                introspectedTable.getFullyQualifiedTable().toString()));
        tlc = getBaseRecord(introspectedTable);
        if (tlc != null) {
            if (plugins.modelBaseRecordClassGenerated(tlc, introspectedTable)) {
                GeneratedJavaFile gjf = new GeneratedJavaFile(tlc, targetProject);
                list.add(gjf);
            }
        }

        callback.startSubTask(Messages.getString(
                "Progress.9", //$NON-NLS-1$
                introspectedTable.getFullyQualifiedTable().toString()));
        tlc = getRecordWithBLOBs(introspectedTable);
        if (tlc != null) {
            if (plugins.modelRecordWithBLOBsClassGenerated(tlc, introspectedTable)) {
                GeneratedJavaFile gjf = new GeneratedJavaFile(tlc, targetProject);
                list.add(gjf);
            }
        }

        return list;
    }

    /*
     * (non-Javadoc)
     * 
     * @see org.apache.ibatis.ibator.api.JavaModelGenerator#setWarnings(java.util.List)
     */
    public void setWarnings(List<String> warnings) {
        this.warnings = warnings;
    }

    protected Method getSetNullMethod(ColumnDefinition cd) {
        return getNoValueMethod(cd, "IsNull", "is null"); //$NON-NLS-1$ //$NON-NLS-2$
    }

    protected Method getSetNotNullMethod(ColumnDefinition cd) {
        return getNoValueMethod(cd, "IsNotNull", "is not null"); //$NON-NLS-1$ //$NON-NLS-2$
    }

    protected Method getSetEqualMethod(ColumnDefinition cd) {
        return getSingleValueMethod(cd, "EqualTo", "="); //$NON-NLS-1$ //$NON-NLS-2$
    }

    protected Method getSetNotEqualMethod(ColumnDefinition cd) {
        return getSingleValueMethod(cd, "NotEqualTo", "<>"); //$NON-NLS-1$ //$NON-NLS-2$
    }

    protected Method getSetGreaterThanMethod(ColumnDefinition cd) {
        return getSingleValueMethod(cd, "GreaterThan", ">"); //$NON-NLS-1$ //$NON-NLS-2$
    }

    protected Method getSetGreaterThenOrEqualMethod(ColumnDefinition cd) {
        return getSingleValueMethod(cd, "GreaterThanOrEqualTo", ">="); //$NON-NLS-1$ //$NON-NLS-2$
    }

    protected Method getSetLessThanMethod(ColumnDefinition cd) {
        return getSingleValueMethod(cd, "LessThan", "<"); //$NON-NLS-1$ //$NON-NLS-2$
    }

    protected Method getSetLessThanOrEqualMethod(ColumnDefinition cd) {
        return getSingleValueMethod(cd, "LessThanOrEqualTo", "<="); //$NON-NLS-1$ //$NON-NLS-2$
    }

    protected Method getSetLikeMethod(ColumnDefinition cd) {
        return getSingleValueMethod(cd, "Like", "like"); //$NON-NLS-1$ //$NON-NLS-2$
    }

    protected Method getSetNotLikeMethod(ColumnDefinition cd) {
        return getSingleValueMethod(cd, "NotLike", "not like"); //$NON-NLS-1$ //$NON-NLS-2$
    }

    protected Method getSingleValueMethod(ColumnDefinition cd, String nameFragment, String operator) {
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.addParameter(new Parameter(cd.getResolvedJavaType()
                .getFullyQualifiedJavaType(), "value")); //$NON-NLS-1$
        StringBuffer sb = new StringBuffer();
        sb.append(cd.getJavaProperty());
        sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
        sb.insert(0, "and"); //$NON-NLS-1$
        sb.append(nameFragment);
        method.setName(sb.toString());
        method.setReturnType(FullyQualifiedJavaType.getCriteriaInstance());
        sb.setLength(0);
    
        if (cd.isJDBCDateColumn()) {
            sb.append("addCriterionForJDBCDate(\""); //$NON-NLS-1$
        } else if (cd.isJDBCTimeColumn()) {
            sb.append("addCriterionForJDBCTime(\""); //$NON-NLS-1$
        } else if (StringUtility.stringHasValue(cd.getTypeHandler())) {
            sb.append("add"); //$NON-NLS-1$
            sb.append(cd.getJavaProperty());
            sb.setCharAt(3, Character.toUpperCase(sb.charAt(3)));
            sb.append("Criterion(\""); //$NON-NLS-1$
        } else {
            sb.append("addCriterion(\""); //$NON-NLS-1$
        }
    
        sb.append(cd.getAliasedActualColumnName());
        sb.append(' ');
        sb.append(operator);
        sb.append("\", "); //$NON-NLS-1$
        
        if (cd.getResolvedJavaType().getFullyQualifiedJavaType().isPrimitive()) {
            sb.append("new "); //$NON-NLS-1$
            sb.append(cd.getResolvedJavaType().getFullyQualifiedJavaType()
                    .getPrimitiveTypeWrapper().getShortName());
            sb.append("(value)"); //$NON-NLS-1$
        } else {
            sb.append("value"); //$NON-NLS-1$
        }
        
        sb.append(", \""); //$NON-NLS-1$
        sb.append(cd.getJavaProperty());
        sb.append("\");"); //$NON-NLS-1$
        method.addBodyLine(sb.toString());
        method.addBodyLine("return this;"); //$NON-NLS-1$
    
        return method;
    }

    protected Method getNoValueMethod(ColumnDefinition cd, String nameFragment, String operator) {
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        if (!(this instanceof JavaModelGeneratorJava5Impl)) {
            if (ibatorContext.getSuppressTypeWarnings()) {
                method.addSuppressTypeWarningsAnnotation();
            }
        }
        StringBuffer sb = new StringBuffer();
        sb.append(cd.getJavaProperty());
        sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
        sb.insert(0, "and"); //$NON-NLS-1$
        sb.append(nameFragment);
        method.setName(sb.toString());
        method.setReturnType(FullyQualifiedJavaType.getCriteriaInstance());
        sb.setLength(0);
        sb.append("addCriterion(\""); //$NON-NLS-1$
        sb.append(cd.getAliasedActualColumnName());
        sb.append(' ');
        sb.append(operator);
        sb.append("\");"); //$NON-NLS-1$
        method.addBodyLine(sb.toString());
        method.addBodyLine("return this;"); //$NON-NLS-1$
    
        return method;
    }

    /**
     * Generates methods that set between and not between conditions
     * 
     * @param cd
     * @param betweenMethod
     * @return a generated method for the between or not between method
     */
    protected Method getSetBetweenOrNotBetweenMethod(ColumnDefinition cd, boolean betweenMethod) {
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        FullyQualifiedJavaType type = cd.getResolvedJavaType().getFullyQualifiedJavaType();
        
        method.addParameter(new Parameter(type, "value1")); //$NON-NLS-1$
        method.addParameter(new Parameter(type, "value2")); //$NON-NLS-1$
        StringBuffer sb = new StringBuffer();
        sb.append(cd.getJavaProperty());
        sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
        sb.insert(0, "and"); //$NON-NLS-1$
        if (betweenMethod) {
            sb.append("Between"); //$NON-NLS-1$
        } else {
            sb.append("NotBetween"); //$NON-NLS-1$
        }
        method.setName(sb.toString());
        method.setReturnType(FullyQualifiedJavaType.getCriteriaInstance());
        sb.setLength(0);
    
        if (cd.isJDBCDateColumn()) {
            sb.append("addCriterionForJDBCDate(\""); //$NON-NLS-1$
        } else if (cd.isJDBCTimeColumn()) {
            sb.append("addCriterionForJDBCTime(\""); //$NON-NLS-1$
        } else if (StringUtility.stringHasValue(cd.getTypeHandler())) {
            sb.append("add"); //$NON-NLS-1$
            sb.append(cd.getJavaProperty());
            sb.setCharAt(3, Character.toUpperCase(sb.charAt(3)));
            sb.append("Criterion(\""); //$NON-NLS-1$
        } else {
            sb.append("addCriterion(\""); //$NON-NLS-1$
        }
    
        sb.append(cd.getAliasedActualColumnName());
        if (betweenMethod) {
            sb.append(" between"); //$NON-NLS-1$
        } else {
            sb.append(" not between"); //$NON-NLS-1$
        }
        sb.append("\", "); //$NON-NLS-1$
        if (cd.getResolvedJavaType().getFullyQualifiedJavaType().isPrimitive()) {
            sb.append("new "); //$NON-NLS-1$
            sb.append(cd.getResolvedJavaType().getFullyQualifiedJavaType()
                    .getPrimitiveTypeWrapper().getShortName());
            sb.append("(value1), "); //$NON-NLS-1$
            sb.append("new "); //$NON-NLS-1$
            sb.append(cd.getResolvedJavaType().getFullyQualifiedJavaType()
                    .getPrimitiveTypeWrapper().getShortName());
            sb.append("(value2)"); //$NON-NLS-1$
        } else {
            sb.append("value1, value2"); //$NON-NLS-1$
        }
        
        sb.append(", \""); //$NON-NLS-1$
        sb.append(cd.getJavaProperty());
        sb.append("\");"); //$NON-NLS-1$
        method.addBodyLine(sb.toString());
        method.addBodyLine("return this;"); //$NON-NLS-1$
    
        return method;
    }

    protected TopLevelClass getExample(IntrospectedTable introspectedTable) {
        if (!introspectedTable.getRules().generateExampleClass()) {
            return null;
        }
        
        CommentGenerator commentGenerator = ibatorContext.getCommentGenerator();

        FullyQualifiedTable table = introspectedTable.getFullyQualifiedTable();
        FullyQualifiedJavaType type = introspectedTable.getExampleType();
        TopLevelClass topLevelClass = new TopLevelClass(type);
        topLevelClass.setVisibility(JavaVisibility.PUBLIC);
        commentGenerator.addJavaFileComment(topLevelClass);

        // add default constructor
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setConstructor(true);
        method.setName(type.getShortName());
        method.addBodyLine("oredCriteria = new ArrayList();"); //$NON-NLS-1$
        commentGenerator.addGeneralMethodComment(method, table);
        topLevelClass.addMethod(method);
        
        // add shallow copy contructor if the update by
        // example methods are enabled - because the parameter
        // class for update by example methods will subclass this class
        IbatorRules rules = introspectedTable.getRules();
        if (rules.generateUpdateByExampleSelective()
                || rules.generateUpdateByExampleWithBLOBs()
                ||rules.generateUpdateByExampleWithoutBLOBs()) {
            method = new Method();
            method.setVisibility(JavaVisibility.PROTECTED);
            method.setConstructor(true);
            method.setName(type.getShortName());
            method.addParameter(new Parameter(type, "example")); //$NON-NLS-1$
            method.addBodyLine("this.orderByClause = example.orderByClause;"); //$NON-NLS-1$
            method.addBodyLine("this.oredCriteria = example.oredCriteria;"); //$NON-NLS-1$
            commentGenerator.addGeneralMethodComment(method, table);
            topLevelClass.addMethod(method);
        }
        
        // add field, getter, setter for orderby clause
        Field field = new Field();
        field.setVisibility(JavaVisibility.PROTECTED);
        field.setType(FullyQualifiedJavaType.getStringInstance());
        field.setName("orderByClause"); //$NON-NLS-1$
        commentGenerator.addFieldComment(field, table);
        topLevelClass.addField(field);

        method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setName("setOrderByClause"); //$NON-NLS-1$
        method.addParameter(new Parameter(FullyQualifiedJavaType
                .getStringInstance(), "orderByClause")); //$NON-NLS-1$
        method.addBodyLine("this.orderByClause = orderByClause;"); //$NON-NLS-1$
        commentGenerator.addGeneralMethodComment(method, table);
        topLevelClass.addMethod(method);

        method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(FullyQualifiedJavaType.getStringInstance());
        method.setName("getOrderByClause"); //$NON-NLS-1$
        method.addBodyLine("return orderByClause;"); //$NON-NLS-1$
        commentGenerator.addGeneralMethodComment(method, table);
        topLevelClass.addMethod(method);

        // add field and methods for the list of ored criteria
        field = new Field();
        field.setVisibility(JavaVisibility.PROTECTED);

        FullyQualifiedJavaType fqjt = FullyQualifiedJavaType
                .getNewListInstance();

        field.setType(fqjt);
        field.setName("oredCriteria"); //$NON-NLS-1$
        commentGenerator.addFieldComment(field, table);
        topLevelClass.addField(field);

        method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(fqjt);
        method.setName("getOredCriteria"); //$NON-NLS-1$
        method.addBodyLine("return oredCriteria;"); //$NON-NLS-1$
        commentGenerator.addGeneralMethodComment(method, table);
        topLevelClass.addMethod(method);

        method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        if (ibatorContext.getSuppressTypeWarnings()) {
            method.addSuppressTypeWarningsAnnotation();
        }
        method.setName("or"); //$NON-NLS-1$
        method.addParameter(new Parameter(FullyQualifiedJavaType.getCriteriaInstance(),
                "criteria")); //$NON-NLS-1$
        method.addBodyLine("oredCriteria.add(criteria);"); //$NON-NLS-1$

        commentGenerator.addGeneralMethodComment(method, table);
        topLevelClass.addMethod(method);
        
        method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        if (ibatorContext.getSuppressTypeWarnings()) {
            method.addSuppressTypeWarningsAnnotation();
        }
        method.setName("createCriteria"); //$NON-NLS-1$
        method.setReturnType(FullyQualifiedJavaType.getCriteriaInstance());
        method.addBodyLine("Criteria criteria = createCriteriaInternal();"); //$NON-NLS-1$
        method.addBodyLine("if (oredCriteria.size() == 0) {"); //$NON-NLS-1$
        method.addBodyLine("oredCriteria.add(criteria);"); //$NON-NLS-1$
        method.addBodyLine("}"); //$NON-NLS-1$
        method.addBodyLine("return criteria;"); //$NON-NLS-1$
        commentGenerator.addGeneralMethodComment(method, table);
        topLevelClass.addMethod(method);
        
        method = new Method();
        method.setVisibility(JavaVisibility.PROTECTED);
        method.setName("createCriteriaInternal"); //$NON-NLS-1$
        method.setReturnType(FullyQualifiedJavaType.getCriteriaInstance());
        method.addBodyLine("Criteria criteria = new Criteria();"); //$NON-NLS-1$
        method.addBodyLine("return criteria;"); //$NON-NLS-1$
        commentGenerator.addGeneralMethodComment(method, table);
        topLevelClass.addMethod(method);

        method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setName("clear"); //$NON-NLS-1$
        method.addBodyLine("oredCriteria.clear();"); //$NON-NLS-1$
        commentGenerator.addGeneralMethodComment(method, table);
        topLevelClass.addMethod(method);

        // now generate the inner class that holds the AND conditions
        topLevelClass.addInnerClass(getCriteriaInnerClass(topLevelClass,
                introspectedTable));

        return topLevelClass;
    }

    protected InnerClass getCriteriaInnerClass(
            TopLevelClass topLevelClass, IntrospectedTable introspectedTable) {
        Field field;
        Method method;

        InnerClass answer = new InnerClass(FullyQualifiedJavaType.getCriteriaInstance());

        answer.setVisibility(JavaVisibility.PUBLIC);
        answer.setStatic(true);
        ibatorContext.getCommentGenerator().addClassComment(answer, introspectedTable.getFullyQualifiedTable());

        method = new Method();
        method.setVisibility(JavaVisibility.PROTECTED);
        method.setName("Criteria"); //$NON-NLS-1$
        method.setConstructor(true);
        method.addBodyLine("super();"); //$NON-NLS-1$
        method.addBodyLine("criteriaWithoutValue = new ArrayList();"); //$NON-NLS-1$
        method
                .addBodyLine("criteriaWithSingleValue = new ArrayList();"); //$NON-NLS-1$
        method
                .addBodyLine("criteriaWithListValue = new ArrayList();"); //$NON-NLS-1$
        method
                .addBodyLine("criteriaWithBetweenValue = new ArrayList();"); //$NON-NLS-1$
        answer.addMethod(method);

        List<String> criteriaLists = new ArrayList<String>();
        criteriaLists.add("criteriaWithoutValue"); //$NON-NLS-1$
        criteriaLists.add("criteriaWithSingleValue"); //$NON-NLS-1$
        criteriaLists.add("criteriaWithListValue"); //$NON-NLS-1$
        criteriaLists.add("criteriaWithBetweenValue"); //$NON-NLS-1$

        for (ColumnDefinition cd : introspectedTable.getNonBLOBColumns()) {
            if (StringUtility.stringHasValue(cd.getTypeHandler())) {
                criteriaLists.addAll(
                    addtypeHandledObjectsAndMethods(cd, method, answer));
            }
        }

        // now generate the isValid method
        method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setName("isValid"); //$NON-NLS-1$
        method.setReturnType(FullyQualifiedJavaType.getBooleanPrimitiveInstance());
        StringBuffer sb = new StringBuffer();
        Iterator<String> strIter = criteriaLists.iterator();
        sb.append("return "); //$NON-NLS-1$
        sb.append(strIter.next());
        sb.append(".size() > 0"); //$NON-NLS-1$
        method.addBodyLine(sb.toString());
        while (strIter.hasNext()) {
            sb.setLength(0);
            OutputUtilities.javaIndent(sb, 1);
            sb.append("|| "); //$NON-NLS-1$
            sb.append(strIter.next());
            sb.append(".size() > 0"); //$NON-NLS-1$
            if (!strIter.hasNext()) {
                sb.append(';');
            }
            method.addBodyLine(sb.toString());
        }
        answer.addMethod(method);
        
        // now we need to generate the methods that will be used in the SqlMap
        // to generate the dynamic where clause
        topLevelClass.addImportedType(FullyQualifiedJavaType
                .getNewMapInstance());
        topLevelClass.addImportedType(FullyQualifiedJavaType
                .getNewListInstance());
        topLevelClass.addImportedType(FullyQualifiedJavaType
                .getNewHashMapInstance());
        topLevelClass.addImportedType(FullyQualifiedJavaType
                .getNewArrayListInstance());

        field = new Field();
        field.setVisibility(JavaVisibility.PROTECTED);
        FullyQualifiedJavaType listOfStrings = FullyQualifiedJavaType
                .getNewListInstance();
        field.setType(listOfStrings);
        field.setName("criteriaWithoutValue"); //$NON-NLS-1$
        answer.addField(field);

        method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(field.getType());
        method.setName(JavaBeansUtil.getGetterMethodName(field.getName(), field.getType()));
        method.addBodyLine("return criteriaWithoutValue;"); //$NON-NLS-1$
        answer.addMethod(method);

        FullyQualifiedJavaType listOfMaps = FullyQualifiedJavaType
                .getNewListInstance();

        field = new Field();
        field.setVisibility(JavaVisibility.PROTECTED);
        field.setType(listOfMaps);
        field.setName("criteriaWithSingleValue"); //$NON-NLS-1$
        answer.addField(field);

        method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(field.getType());
        method.setName(JavaBeansUtil.getGetterMethodName(field.getName(), field.getType()));
        method.addBodyLine("return criteriaWithSingleValue;"); //$NON-NLS-1$
        answer.addMethod(method);

        field = new Field();
        field.setVisibility(JavaVisibility.PROTECTED);
        field.setType(listOfMaps);
        field.setName("criteriaWithListValue"); //$NON-NLS-1$
        answer.addField(field);

        method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(field.getType());
        method.setName(JavaBeansUtil.getGetterMethodName(field.getName(), field.getType()));
        method.addBodyLine("return criteriaWithListValue;"); //$NON-NLS-1$
        answer.addMethod(method);

        field = new Field();
        field.setVisibility(JavaVisibility.PROTECTED);
        field.setType(listOfMaps);
        field.setName("criteriaWithBetweenValue"); //$NON-NLS-1$
        answer.addField(field);

        method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(field.getType());
        method.setName(JavaBeansUtil.getGetterMethodName(field.getName(), field.getType()));
        method.addBodyLine("return criteriaWithBetweenValue;"); //$NON-NLS-1$
        answer.addMethod(method);

        // now add the methods for simplifying the individual field set methods
        method = new Method();
        method.setVisibility(JavaVisibility.PROTECTED);
        if (ibatorContext.getSuppressTypeWarnings()) {
            method.addSuppressTypeWarningsAnnotation();
        }
        method.setName("addCriterion"); //$NON-NLS-1$
        method.addParameter(new Parameter(FullyQualifiedJavaType
                .getStringInstance(), "condition")); //$NON-NLS-1$
        method.addBodyLine("if (condition == null) {"); //$NON-NLS-1$
        method
                .addBodyLine("throw new RuntimeException(\"Value for condition cannot be null\");"); //$NON-NLS-1$
        method.addBodyLine("}"); //$NON-NLS-1$
        method.addBodyLine("criteriaWithoutValue.add(condition);"); //$NON-NLS-1$
        answer.addMethod(method);

        method = new Method();
        method.setVisibility(JavaVisibility.PROTECTED);
        if (ibatorContext.getSuppressTypeWarnings()) {
            method.addSuppressTypeWarningsAnnotation();
        }
        method.setName("addCriterion"); //$NON-NLS-1$
        method.addParameter(new Parameter(FullyQualifiedJavaType
                .getStringInstance(), "condition")); //$NON-NLS-1$
        method.addParameter(new Parameter(FullyQualifiedJavaType
                .getObjectInstance(), "value")); //$NON-NLS-1$
        method.addParameter(new Parameter(FullyQualifiedJavaType
                .getStringInstance(), "property")); //$NON-NLS-1$
        method.addBodyLine("if (value == null) {"); //$NON-NLS-1$
        method
                .addBodyLine("throw new RuntimeException(\"Value for \" + property + \" cannot be null\");"); //$NON-NLS-1$
        method.addBodyLine("}"); //$NON-NLS-1$
        method
                .addBodyLine("Map map = new HashMap();"); //$NON-NLS-1$
        method.addBodyLine("map.put(\"condition\", condition);"); //$NON-NLS-1$
        method.addBodyLine("map.put(\"value\", value);"); //$NON-NLS-1$
        method.addBodyLine("criteriaWithSingleValue.add(map);"); //$NON-NLS-1$
        answer.addMethod(method);

        FullyQualifiedJavaType listOfObjects = FullyQualifiedJavaType
                .getNewListInstance();

        method = new Method();
        method.setVisibility(JavaVisibility.PROTECTED);
        if (ibatorContext.getSuppressTypeWarnings()) {
            method.addSuppressTypeWarningsAnnotation();
        }
        method.setName("addCriterion"); //$NON-NLS-1$
        method.addParameter(new Parameter(FullyQualifiedJavaType
                .getStringInstance(), "condition")); //$NON-NLS-1$
        method.addParameter(new Parameter(listOfObjects, "values")); //$NON-NLS-1$
        method.addParameter(new Parameter(FullyQualifiedJavaType
                .getStringInstance(), "property")); //$NON-NLS-1$
        method.addBodyLine("if (values == null || values.size() == 0) {"); //$NON-NLS-1$
        method
                .addBodyLine("throw new RuntimeException(\"Value list for \" + property + \" cannot be null or empty\");"); //$NON-NLS-1$
        method.addBodyLine("}"); //$NON-NLS-1$
        method
                .addBodyLine("Map map = new HashMap();"); //$NON-NLS-1$
        method.addBodyLine("map.put(\"condition\", condition);"); //$NON-NLS-1$
        method.addBodyLine("map.put(\"values\", values);"); //$NON-NLS-1$
        method.addBodyLine("criteriaWithListValue.add(map);"); //$NON-NLS-1$
        answer.addMethod(method);

        method = new Method();
        method.setVisibility(JavaVisibility.PROTECTED);
        if (ibatorContext.getSuppressTypeWarnings()) {
            method.addSuppressTypeWarningsAnnotation();
        }
        method.setName("addCriterion"); //$NON-NLS-1$
        method.addParameter(new Parameter(FullyQualifiedJavaType
                .getStringInstance(), "condition")); //$NON-NLS-1$
        method.addParameter(new Parameter(FullyQualifiedJavaType
                .getObjectInstance(), "value1")); //$NON-NLS-1$
        method.addParameter(new Parameter(FullyQualifiedJavaType
                .getObjectInstance(), "value2")); //$NON-NLS-1$
        method.addParameter(new Parameter(FullyQualifiedJavaType
                .getStringInstance(), "property")); //$NON-NLS-1$
        method.addBodyLine("if (value1 == null || value2 == null) {"); //$NON-NLS-1$
        method
                .addBodyLine("throw new RuntimeException(\"Between values for \" + property + \" cannot be null\");"); //$NON-NLS-1$
        method.addBodyLine("}"); //$NON-NLS-1$
        method.addBodyLine("List list = new ArrayList();"); //$NON-NLS-1$
        method.addBodyLine("list.add(value1);"); //$NON-NLS-1$
        method.addBodyLine("list.add(value2);"); //$NON-NLS-1$
        method
                .addBodyLine("Map map = new HashMap();"); //$NON-NLS-1$
        method.addBodyLine("map.put(\"condition\", condition);"); //$NON-NLS-1$
        method.addBodyLine("map.put(\"values\", list);"); //$NON-NLS-1$
        method.addBodyLine("criteriaWithBetweenValue.add(map);"); //$NON-NLS-1$
        answer.addMethod(method);

        FullyQualifiedJavaType listOfDates = FullyQualifiedJavaType
                .getNewListInstance();

        if (introspectedTable.hasJDBCDateColumns()) {
            topLevelClass.addImportedType(FullyQualifiedJavaType
                    .getDateInstance());
            topLevelClass.addImportedType(FullyQualifiedJavaType
                    .getNewIteratorInstance());
            method = new Method();
            method.setVisibility(JavaVisibility.PROTECTED);
            method.setName("addCriterionForJDBCDate"); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getStringInstance(), "condition")); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getDateInstance(), "value")); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getStringInstance(), "property")); //$NON-NLS-1$
            method
                    .addBodyLine("addCriterion(condition, new java.sql.Date(value.getTime()), property);"); //$NON-NLS-1$
            answer.addMethod(method);

            method = new Method();
            method.setVisibility(JavaVisibility.PROTECTED);
            if (ibatorContext.getSuppressTypeWarnings()) {
                method.addSuppressTypeWarningsAnnotation();
            }
            method.setName("addCriterionForJDBCDate"); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getStringInstance(), "condition")); //$NON-NLS-1$
            method.addParameter(new Parameter(listOfDates, "values")); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getStringInstance(), "property")); //$NON-NLS-1$
            method.addBodyLine("if (values == null || values.size() == 0) {"); //$NON-NLS-1$
            method
                    .addBodyLine("throw new RuntimeException(\"Value list for \" + property + \" cannot be null or empty\");"); //$NON-NLS-1$
            method.addBodyLine("}"); //$NON-NLS-1$
            method
                    .addBodyLine("List dateList = new ArrayList();"); //$NON-NLS-1$
            method.addBodyLine("Iterator iter = values.iterator();"); //$NON-NLS-1$
            method.addBodyLine("while (iter.hasNext()) {"); //$NON-NLS-1$
            method
                    .addBodyLine("dateList.add(new java.sql.Date(((Date)iter.next()).getTime()));"); //$NON-NLS-1$
            method.addBodyLine("}"); //$NON-NLS-1$
            method
                    .addBodyLine("addCriterion(condition, dateList, property);"); //$NON-NLS-1$
            answer.addMethod(method);

            method = new Method();
            method.setVisibility(JavaVisibility.PROTECTED);
            method.setName("addCriterionForJDBCDate"); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getStringInstance(), "condition")); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getDateInstance(), "value1")); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getDateInstance(), "value2")); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getStringInstance(), "property")); //$NON-NLS-1$
            method.addBodyLine("if (value1 == null || value2 == null) {"); //$NON-NLS-1$
            method
                    .addBodyLine("throw new RuntimeException(\"Between values for \" + property + \" cannot be null\");"); //$NON-NLS-1$
            method.addBodyLine("}"); //$NON-NLS-1$
            method
                    .addBodyLine("addCriterion(condition, new java.sql.Date(value1.getTime()), new java.sql.Date(value2.getTime()), property);"); //$NON-NLS-1$
            answer.addMethod(method);
        }

        if (introspectedTable.hasJDBCTimeColumns()) {
            topLevelClass.addImportedType(FullyQualifiedJavaType
                    .getDateInstance());
            topLevelClass.addImportedType(FullyQualifiedJavaType
                    .getNewIteratorInstance());
            method = new Method();
            method.setVisibility(JavaVisibility.PROTECTED);
            method.setName("addCriterionForJDBCTime"); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getStringInstance(), "condition")); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getDateInstance(), "value")); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getStringInstance(), "property")); //$NON-NLS-1$
            method
                    .addBodyLine("addCriterion(condition, new java.sql.Time(value.getTime()), property);"); //$NON-NLS-1$
            answer.addMethod(method);

            method = new Method();
            method.setVisibility(JavaVisibility.PROTECTED);
            if (ibatorContext.getSuppressTypeWarnings()) {
                method.addSuppressTypeWarningsAnnotation();
            }
            method.setName("addCriterionForJDBCTime"); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getStringInstance(), "condition")); //$NON-NLS-1$
            method.addParameter(new Parameter(listOfDates, "values")); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getStringInstance(), "property")); //$NON-NLS-1$
            method.addBodyLine("if (values == null || values.size() == 0) {"); //$NON-NLS-1$
            method
                    .addBodyLine("throw new RuntimeException(\"Value list for \" + property + \" cannot be null or empty\");"); //$NON-NLS-1$
            method.addBodyLine("}"); //$NON-NLS-1$
            method
                    .addBodyLine("List dateList = new ArrayList();"); //$NON-NLS-1$
            method.addBodyLine("Iterator iter = values.iterator();"); //$NON-NLS-1$
            method.addBodyLine("while (iter.hasNext()) {"); //$NON-NLS-1$
            method
                    .addBodyLine("dateList.add(new java.sql.Time(((Date)iter.next()).getTime()));"); //$NON-NLS-1$
            method.addBodyLine("}"); //$NON-NLS-1$
            method
                    .addBodyLine("addCriterion(condition, dateList, property);"); //$NON-NLS-1$
            answer.addMethod(method);

            method = new Method();
            method.setVisibility(JavaVisibility.PROTECTED);
            method.setName("addCriterionForJDBCTime"); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getStringInstance(), "condition")); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getDateInstance(), "value1")); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getDateInstance(), "value2")); //$NON-NLS-1$
            method.addParameter(new Parameter(FullyQualifiedJavaType
                    .getStringInstance(), "property")); //$NON-NLS-1$
            method.addBodyLine("if (value1 == null || value2 == null) {"); //$NON-NLS-1$
            method
                    .addBodyLine("throw new RuntimeException(\"Between values for \" + property + \" cannot be null\");"); //$NON-NLS-1$
            method.addBodyLine("}"); //$NON-NLS-1$
            method
                    .addBodyLine("addCriterion(condition, new java.sql.Time(value1.getTime()), new java.sql.Time(value2.getTime()), property);"); //$NON-NLS-1$
            answer.addMethod(method);
        }

        for (ColumnDefinition cd : introspectedTable.getNonBLOBColumns()) {
            topLevelClass.addImportedType(cd.getResolvedJavaType()
                    .getFullyQualifiedJavaType());

            // here we need to add the individual methods for setting the
            // conditions for a field
            answer.addMethod(getSetNullMethod(cd));
            answer.addMethod(getSetNotNullMethod(cd));
            answer.addMethod(getSetEqualMethod(cd));
            answer.addMethod(getSetNotEqualMethod(cd));
            answer.addMethod(getSetGreaterThanMethod(cd));
            answer.addMethod(getSetGreaterThenOrEqualMethod(cd));
            answer.addMethod(getSetLessThanMethod(cd));
            answer.addMethod(getSetLessThanOrEqualMethod(cd));

            if (cd.isJdbcCharacterColumn()) {
                answer.addMethod(getSetLikeMethod(cd));
                answer.addMethod(getSetNotLikeMethod(cd));
            }

            answer.addMethod(getSetInOrNotInMethod(cd, true));
            answer.addMethod(getSetInOrNotInMethod(cd, false));
            answer.addMethod(getSetBetweenOrNotBetweenMethod(cd, true));
            answer.addMethod(getSetBetweenOrNotBetweenMethod(cd, false));
        }

        return answer;
    }

    /**
     * 
     * @param cd
     * @param inMethod
     *            if true generates an "in" method, else generates a "not in"
     *            method
     * @return a generated method for the in or not in method 
     */
    protected Method getSetInOrNotInMethod(ColumnDefinition cd, boolean inMethod) {
        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        FullyQualifiedJavaType type = FullyQualifiedJavaType
                .getNewListInstance();
        method.addParameter(new Parameter(type, "values")); //$NON-NLS-1$
        StringBuffer sb = new StringBuffer();
        sb.append(cd.getJavaProperty());
        sb.setCharAt(0, Character.toUpperCase(sb.charAt(0)));
        sb.insert(0, "and"); //$NON-NLS-1$
        if (inMethod) {
            sb.append("In"); //$NON-NLS-1$
        } else {
            sb.append("NotIn"); //$NON-NLS-1$
        }
        method.setName(sb.toString());
        method.setReturnType(FullyQualifiedJavaType.getCriteriaInstance());
        sb.setLength(0);

        if (cd.isJDBCDateColumn()) {
            sb.append("addCriterionForJDBCDate(\""); //$NON-NLS-1$
        } else if (cd.isJDBCTimeColumn()) {
            sb.append("addCriterionForJDBCTime(\""); //$NON-NLS-1$
        } else if (StringUtility.stringHasValue(cd.getTypeHandler())) {
            sb.append("add"); //$NON-NLS-1$
            sb.append(cd.getJavaProperty());
            sb.setCharAt(3, Character.toUpperCase(sb.charAt(3)));
            sb.append("Criterion(\""); //$NON-NLS-1$
        } else {
            sb.append("addCriterion(\""); //$NON-NLS-1$
        }

        sb.append(cd.getAliasedActualColumnName());
        if (inMethod) {
            sb.append(" in"); //$NON-NLS-1$
        } else {
            sb.append(" not in"); //$NON-NLS-1$
        }
        sb.append("\", values, \""); //$NON-NLS-1$
        sb.append(cd.getJavaProperty());
        sb.append("\");"); //$NON-NLS-1$
        method.addBodyLine(sb.toString());
        method.addBodyLine("return this;"); //$NON-NLS-1$

        return method;
    }

    /**
     * This method adds all the extra methods and fields required 
     * to support a user defined type handler on some column.
     * 
     * @param cd
     * @param constructor
     * @param innerClass
     * @return a list of the names of all Lists added to the class by this method
     */
    private List<String> addtypeHandledObjectsAndMethods(ColumnDefinition cd,
            Method constructor, InnerClass innerClass) {
        List<String> answer = new ArrayList<String>();
        StringBuffer sb = new StringBuffer();

        // add new private fields and public accessors in the class
        FullyQualifiedJavaType listOfMaps = FullyQualifiedJavaType
                .getNewListInstance();
        
        sb.setLength(0);
        sb.append(cd.getJavaProperty());
        sb.append("CriteriaWithSingleValue"); //$NON-NLS-1$
        answer.add(sb.toString());

        Field field = new Field();
        field.setVisibility(JavaVisibility.PROTECTED);
        field.setType(listOfMaps);
        field.setName(sb.toString());
        innerClass.addField(field);

        Method method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(field.getType());
        method.setName(JavaBeansUtil.getGetterMethodName(field.getName(), field.getType()));
        sb.insert(0, "return "); //$NON-NLS-1$
        sb.append(';');
        method.addBodyLine(sb.toString());
        innerClass.addMethod(method);

        sb.setLength(0);
        sb.append(cd.getJavaProperty());
        sb.append("CriteriaWithListValue"); //$NON-NLS-1$
        answer.add(sb.toString());

        field = new Field();
        field.setVisibility(JavaVisibility.PROTECTED);
        field.setType(listOfMaps);
        field.setName(sb.toString());
        innerClass.addField(field);

        method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(field.getType());
        method.setName(JavaBeansUtil.getGetterMethodName(field.getName(), field.getType()));
        sb.insert(0, "return "); //$NON-NLS-1$
        sb.append(';');
        method.addBodyLine(sb.toString());
        innerClass.addMethod(method);

        sb.setLength(0);
        sb.append(cd.getJavaProperty());
        sb.append("CriteriaWithBetweenValue"); //$NON-NLS-1$
        answer.add(sb.toString());
        
        field = new Field();
        field.setVisibility(JavaVisibility.PROTECTED);
        field.setType(listOfMaps);
        field.setName(sb.toString());
        innerClass.addField(field);

        method = new Method();
        method.setVisibility(JavaVisibility.PUBLIC);
        method.setReturnType(field.getType());
        method.setName(JavaBeansUtil.getGetterMethodName(field.getName(), field.getType()));
        sb.insert(0, "return "); //$NON-NLS-1$
        sb.append(';');
        method.addBodyLine(sb.toString());
        innerClass.addMethod(method);

        // add constructor initialization
        sb.setLength(0);
        sb.append(cd.getJavaProperty());
        sb
                .append("CriteriaWithSingleValue = new ArrayList();"); //$NON-NLS-1$;
        constructor.addBodyLine(sb.toString());

        sb.setLength(0);
        sb.append(cd.getJavaProperty());
        sb
                .append("CriteriaWithListValue = new ArrayList();"); //$NON-NLS-1$
        constructor.addBodyLine(sb.toString());

        sb.setLength(0);
        sb.append(cd.getJavaProperty());
        sb
                .append("CriteriaWithBetweenValue = new ArrayList();"); //$NON-NLS-1$
        constructor.addBodyLine(sb.toString());

        // now add the methods for simplifying the individual field set methods
        method = new Method();
        method.setVisibility(JavaVisibility.PROTECTED);
        sb.setLength(0);
        sb.append("add"); //$NON-NLS-1$
        sb.append(cd.getJavaProperty());
        sb.setCharAt(3, Character.toUpperCase(sb.charAt(3)));
        sb.append("Criterion"); //$NON-NLS-1$
        
        method.setName(sb.toString());
        method.addParameter(new Parameter(FullyQualifiedJavaType
                .getStringInstance(), "condition")); //$NON-NLS-1$
        method.addParameter(new Parameter(cd.getResolvedJavaType().getFullyQualifiedJavaType(), "value")); //$NON-NLS-1$
        method.addParameter(new Parameter(FullyQualifiedJavaType
                .getStringInstance(), "property")); //$NON-NLS-1$
        method.addBodyLine("if (value == null) {"); //$NON-NLS-1$
        method
                .addBodyLine("throw new RuntimeException(\"Value for \" + property + \" cannot be null\");"); //$NON-NLS-1$
        method.addBodyLine("}"); //$NON-NLS-1$
        method
                .addBodyLine("Map map = new HashMap();"); //$NON-NLS-1$
        method.addBodyLine("map.put(\"condition\", condition);"); //$NON-NLS-1$
        method.addBodyLine("map.put(\"value\", value);"); //$NON-NLS-1$
        
        sb.setLength(0);
        sb.append(cd.getJavaProperty());
        sb.append("CriteriaWithSingleValue.add(map);"); //$NON-NLS-1$
        method.addBodyLine(sb.toString());
        innerClass.addMethod(method);

        FullyQualifiedJavaType listOfObjects = FullyQualifiedJavaType
                .getNewListInstance();

        sb.setLength(0);
        sb.append("add"); //$NON-NLS-1$
        sb.append(cd.getJavaProperty());
        sb.setCharAt(3, Character.toUpperCase(sb.charAt(3)));
        sb.append("Criterion"); //$NON-NLS-1$
        
        method = new Method();
        method.setVisibility(JavaVisibility.PROTECTED);
        method.setName(sb.toString());
        method.addParameter(new Parameter(FullyQualifiedJavaType
                .getStringInstance(), "condition")); //$NON-NLS-1$
        method.addParameter(new Parameter(listOfObjects, "values")); //$NON-NLS-1$
        method.addParameter(new Parameter(FullyQualifiedJavaType
                .getStringInstance(), "property")); //$NON-NLS-1$
        method.addBodyLine("if (values == null || values.size() == 0) {"); //$NON-NLS-1$
        method
                .addBodyLine("throw new RuntimeException(\"Value list for \" + property + \" cannot be null or empty\");"); //$NON-NLS-1$
        method.addBodyLine("}"); //$NON-NLS-1$
        method
                .addBodyLine("Map map = new HashMap();"); //$NON-NLS-1$
        method.addBodyLine("map.put(\"condition\", condition);"); //$NON-NLS-1$
        method.addBodyLine("map.put(\"values\", values);"); //$NON-NLS-1$
        
        sb.setLength(0);
        sb.append(cd.getJavaProperty());
        sb.append("CriteriaWithListValue.add(map);"); //$NON-NLS-1$
        method.addBodyLine(sb.toString());
        innerClass.addMethod(method);

        sb.setLength(0);
        sb.append("add"); //$NON-NLS-1$
        sb.append(cd.getJavaProperty());
        sb.setCharAt(3, Character.toUpperCase(sb.charAt(3)));
        sb.append("Criterion"); //$NON-NLS-1$

        method = new Method();
        method.setVisibility(JavaVisibility.PROTECTED);
        method.setName(sb.toString());
        method.addParameter(new Parameter(FullyQualifiedJavaType
                .getStringInstance(), "condition")); //$NON-NLS-1$
        method.addParameter(new Parameter(cd.getResolvedJavaType().getFullyQualifiedJavaType(), "value1")); //$NON-NLS-1$
        method.addParameter(new Parameter(cd.getResolvedJavaType().getFullyQualifiedJavaType(), "value2")); //$NON-NLS-1$
        method.addParameter(new Parameter(FullyQualifiedJavaType
                .getStringInstance(), "property")); //$NON-NLS-1$
        method.addBodyLine("if (value1 == null || value2 == null) {"); //$NON-NLS-1$
        method
                .addBodyLine("throw new RuntimeException(\"Between values for \" + property + \" cannot be null\");"); //$NON-NLS-1$
        method.addBodyLine("}"); //$NON-NLS-1$
        method.addBodyLine("List list = new ArrayList();"); //$NON-NLS-1$
        method.addBodyLine("list.add(value1);"); //$NON-NLS-1$
        method.addBodyLine("list.add(value2);"); //$NON-NLS-1$
        method
                .addBodyLine("Map map = new HashMap();"); //$NON-NLS-1$
        method.addBodyLine("map.put(\"condition\", condition);"); //$NON-NLS-1$
        method.addBodyLine("map.put(\"values\", list);"); //$NON-NLS-1$
        
        sb.setLength(0);
        sb.append(cd.getJavaProperty());
        sb.append("CriteriaWithBetweenValue.add(map);"); //$NON-NLS-1$
        method.addBodyLine(sb.toString());
        innerClass.addMethod(method);
        
        return answer;
    }

    public void setIbatorContext(IbatorContext ibatorContext) {
        this.ibatorContext = ibatorContext;
    }
    
    protected String getRootClass (IntrospectedTable introspectedTable) {
        String rootClass = introspectedTable.getTableConfigurationProperty(PropertyRegistry.ANY_ROOT_CLASS);
        if (rootClass == null) {
            rootClass = properties.getProperty(PropertyRegistry.ANY_ROOT_CLASS);
        }
        
        return rootClass;
    }
    
    // Ashok's enhancement for checking parent classes...
    protected PropertyDescriptor[] getRootClassPropertyDescriptors(String rootClass) {
        if (rootClass == null) {
            return null;
        }
        
        if (propertyDescriptorMap.containsKey(rootClass)) {
            return propertyDescriptorMap.get(rootClass);
        }
        
        // new class not in map already
        
        Class<?> clazz;
        try {
            clazz = IbatorObjectFactory.loadClass(rootClass);
        } catch (Exception e) {
            propertyDescriptorMap.put(rootClass, null);
            warnings.add(Messages.getString("Warning.20", rootClass)); //$NON-NLS-1$
            return null;
        }
        
        BeanInfo bi;
        try {
            bi = Introspector.getBeanInfo(clazz);
        } catch (IntrospectionException e) {
            propertyDescriptorMap.put(rootClass, null);
            warnings.add(Messages.getString("Warning.20", rootClass)); //$NON-NLS-1$
            return null;
        }
        
        PropertyDescriptor[] pd = bi.getPropertyDescriptors();
        propertyDescriptorMap.put(rootClass, pd);
        return pd;
    }
    
    protected boolean propertyExistsInRootClass(ColumnDefinition columnDefinition, String rootClass) {
        PropertyDescriptor[] rootClassProperties = getRootClassPropertyDescriptors(rootClass);
        if (rootClassProperties == null) {
            return false;
        }
        
        boolean found = false;
        String propertyName = columnDefinition.getJavaProperty();
        String propertyType = columnDefinition.getResolvedJavaType().getFullyQualifiedJavaType().getFullyQualifiedName();
        
        // get method names from class and check against this column definition.
        // better yet, have a map of method Names. check against it.
        for (int i = 0; i < rootClassProperties.length; i++) {
            if (rootClassProperties[i].getName().equals(propertyName)) {
                // property is in the rootClass...

                // Is it the proper type?
                if (!rootClassProperties[i].getPropertyType().getName().equals(propertyType)) {
                    warnings.add(Messages.getString("Warning.21", //$NON-NLS-1$
                            propertyName, rootClass, propertyType));
                    break;
                }
                
                // Does it have a getter?
                if (rootClassProperties[i].getReadMethod() == null) {
                    warnings.add(Messages.getString("Warning.22", //$NON-NLS-1$
                            propertyName, rootClass));
                    break;
                }
                
                // Does it have a setter?
                if (rootClassProperties[i].getWriteMethod() == null) {
                    warnings.add(Messages.getString("Warning.23", //$NON-NLS-1$
                            propertyName, rootClass));
                    break;
                }
                
                found = true;
                break;
            }
        }

        return found;
    }
}
